import bpy
from bpy.types import Object
from typing import List
from mathutils import Vector
from .basics import set_active_object, enter_edit_mode, enter_object_mode


################################################
# generate bounding box for multiple objects   #
################################################


def create_vertex(context, verts=[Vector((0.0, 0.0, 0.0))], name="BoundingBox"):
    me = bpy.data.meshes.new(name + "_Mesh")
    obj = bpy.data.objects.new(name, me)
    context.collection.objects.link(obj)
    me.from_pydata(verts, [], [])
    me.update()
    return obj


def get_bounds(context, objects, where):
    if objects:

        bounds_x = []
        bounds_y = []
        bounds_z = []
        depsgraph = context.evaluated_depsgraph_get()

        if where == "Start":
            context.scene.frame_set(context.scene.frame_start)

            for obj in objects:
                if obj:
                    obj_eval = obj.evaluated_get(depsgraph)
                    for i, bound in enumerate(obj_eval.bound_box):
                        bounds_x.append(bound[0] + obj_eval.matrix_world.translation.x)
                        bounds_y.append(bound[1] + obj_eval.matrix_world.translation.y)
                        bounds_z.append(bound[2] + obj_eval.matrix_world.translation.z)

        elif where == "End":
            context.scene.frame_set(context.scene.frame_end)

            for obj in objects:
                obj_eval = obj.evaluated_get(depsgraph)
                for i, bound in enumerate(obj_eval.bound_box):
                    bounds_x.append(bound[0] + obj_eval.matrix_world.translation.x)
                    bounds_y.append(bound[1] + obj_eval.matrix_world.translation.y)
                    bounds_z.append(bound[2] + obj_eval.matrix_world.translation.z)

        elif where == "StartEnd":
            # start
            context.scene.frame_set(context.scene.frame_start)

            for obj in objects:
                obj_eval = obj.evaluated_get(depsgraph)
                for i, bound in enumerate(obj_eval.bound_box):
                    bounds_x.append(bound[0] + obj_eval.matrix_world.translation.x)
                    bounds_y.append(bound[1] + obj_eval.matrix_world.translation.y)
                    bounds_z.append(bound[2] + obj_eval.matrix_world.translation.z)

            # end
            context.scene.frame_set(context.scene.frame_end)

            for obj in objects:
                obj_eval = obj.evaluated_get(depsgraph)
                for i, bound in enumerate(obj_eval.bound_box):
                    bounds_x.append(bound[0] + obj_eval.matrix_world.translation.x)
                    bounds_y.append(bound[1] + obj_eval.matrix_world.translation.y)
                    bounds_z.append(bound[2] + obj_eval.matrix_world.translation.z)

        return bounds_x, bounds_y, bounds_z


def get_min_and_max_bounds(bounds_x, bounds_y, bounds_z, clamp):
    # get the mins and max from boundings boxes:
    min_x = bounds_x[bounds_x.index(min(bounds_x))]
    max_x = bounds_x[bounds_x.index(max(bounds_x))]

    min_y = bounds_y[bounds_y.index(min(bounds_y))]
    max_y = bounds_y[bounds_y.index(max(bounds_y))]

    min_z = bounds_z[bounds_z.index(min(bounds_z))]
    max_z = bounds_z[bounds_z.index(max(bounds_z))]

    # if have clamp value, clamp it:
    if clamp:
        positive_clamp = clamp
        negative_clamp = (clamp * -1)
        if max_x > positive_clamp:
            max_x = positive_clamp
        if max_y > positive_clamp:
            max_y = positive_clamp
        if max_z > positive_clamp:
            max_z = positive_clamp

        if min_x < negative_clamp:
            min_x = negative_clamp
        if min_y < negative_clamp:
            min_y = negative_clamp
        if min_z < negative_clamp:
            min_z = negative_clamp

    # print(max_x, min_x)

    return min_x, max_x, min_y, max_y, min_z, max_z

def generate_bounding_box(
        self,
        context,
        objects: List[Object],
        bound_type="Empty",
        with_parent=True,
        offset_x=0.8, offset_y=0.8, offset_z=0.8,
        bb_name="BoundingBox", where="Start",
        clamp=False) -> Object:

    if with_parent:
        for obj in objects:

            if obj.parent:
                self.report({'WARNING'}, "They already have a parent!")
                return

    bpy.ops.object.select_all(action='DESELECT')

    bounds_x, bounds_y, bounds_z = get_bounds(context, objects, where)
    min_x, max_x, min_y, max_y, min_z, max_z = get_min_and_max_bounds(bounds_x, bounds_y, bounds_z, clamp)

    verts = []

    if clamp:

        min_value_supported_z = 4

        if min_z < 0:
            min_z = 0

        if min_z - max_z <= min_value_supported_z:
            max_z = min_z + min_value_supported_z

        if max_x <= min_x:
            max_x += min_value_supported_z

        if max_y <= min_y:
            max_y += min_value_supported_z

    # create the 8 vertex for convex hull:
    coords = Vector((min_x, min_y, min_z))
    verts.append(coords)
    #
    coords = Vector((max_x, min_y, min_z))
    verts.append(coords)
    #
    coords = Vector((max_x, max_y, min_z))
    verts.append(coords)
    #
    coords = Vector((min_x, max_y, min_z))
    verts.append(coords)
    #
    coords = Vector((max_x, max_y, max_z))
    verts.append(coords)
    #
    coords = Vector((min_x, min_y, max_z))
    verts.append(coords)
    #
    coords = Vector((max_x, min_y, max_z))
    verts.append(coords)
    #
    coords = Vector((min_x, max_y, max_z))
    verts.append(coords)

    bb_obj = create_vertex(context, verts, bb_name)

    bpy.ops.object.select_all(action='DESELECT')

    # create convex hull
    if not bb_obj:
        return

    bb_obj.select_set(True)
    set_active_object(context, bb_obj)
    enter_object_mode(context)
    bb_obj.display_type = 'WIRE'
    bpy.ops.object.origin_set(type='ORIGIN_GEOMETRY', center='MEDIAN')
    enter_edit_mode(context)
    bpy.ops.mesh.select_all(action='SELECT')
    bpy.ops.mesh.convex_hull()
    enter_object_mode(context)
    # end create convex hull

    if bound_type == "Empty":
        # Create emprty
        bpy.ops.object.empty_add(
            type='CUBE', align='WORLD', location=bb_obj.location, scale=(1, 1, 1))
        empty = bpy.context.active_object

        # Adjust empty dimensions:
        bpy.ops.object.select_all(action='DESELECT')
        empty.select_set(True)
        bpy.ops.object.scale_clear(clear_delta=False)
        empty.scale.x = (bb_obj.dimensions.x / 2) + offset_x
        empty.scale.y = (bb_obj.dimensions.y / 2) + offset_y
        empty.scale.z = (bb_obj.dimensions.z / 2) + offset_z
        empty.name = bb_name

        bpy.ops.object.select_all(action='DESELECT')
        # remove obj tmp:
        bb_obj.select_set(True)
        bpy.ops.object.delete(use_global=False)
        father = empty
    else:
        domain = bb_obj
        bpy.ops.object.scale_clear(clear_delta=False)
        domain.scale.x += offset_x
        domain.scale.y += offset_y
        domain.scale.z += offset_z
        domain.name = bb_name
        father = bb_obj

    bpy.ops.object.transform_apply(
        location=False, rotation=True, scale=True)

    # parent:
    if with_parent:
        for obj in objects:

            if obj.name != father.name:
                obj.parent = father
                obj.matrix_parent_inverse = father.matrix_world.inverted()

    if father:
        return father
